import pickle, os, shutil, pathlib, music_tag

from protolib import remove_empty_folders, correct_path

from data import database, config

class TemplateData(dict):
    def __getitem__(self, key):
        if key in self:
            return super().__getitem__(key)
        else:
            return key

def get_sync_db_template():
    return {
        'type': 'sync',
        'music': {
            'tracks': {},
            'blacklist': [],
            'overwrite': {
                'metadata': True,
                'path': True
            }
        },
        'templates': {
            'path': '%(album)s by %(albumArtist)s/%(title)s by %(artists)s.%(ext)s',
            'artist': {
                'seperator': {
                    'filename': ', ',
                    'metadata': ';'
                }
            }
        }
    }

def sync(path, callback=None, force_meta_write=False, write_changed_meta=False, update_path=True):
    print('Preparing to sync music library')
    os.makedirs(path, exist_ok=True)

    if os.path.exists(os.path.join(path, 'sync.pkl')):
        print('Loading sync database')
        with open(os.path.join(path, 'sync.pkl'), 'rb') as f:
            db = pickle.load(f)
    else:
        db = get_sync_db_template()

    print('Syncing music library')
    added_files = 0
    updated_meta = 0
    moved_files = 0
    removed_files = 0
    running = True
    for uuid in database['music']:
        print('Preparing to proccess %s' % uuid)
        if callable(callback):
            running = callback(uuid)
        track = database['music'][uuid]
        if uuid in db['music']['blacklist']:
            if uuid in db['music']['tracks']:
                if os.path.exists(os.path.join(path, db['music']['tracks'][uuid]['path'])):
                    print('Removing %s' % uuid)
                    os.remove(os.path.join(path, db['music']['tracks'][uuid]['path']))
                    del db['music']['tracks'][uuid]
                    removed_files += 1
            continue
        if uuid not in db['music']['tracks']:
            db['music']['tracks'][uuid] = {
                'path': track['path']
            }
        if not os.path.exists(os.path.join(path, db['music']['tracks'][uuid]['path'])):
            print('Adding file')
            os.makedirs(os.path.join(path, pathlib.Path(track['path']).parent), exist_ok=True)
            shutil.copyfile(
                os.path.join(
                    config['files']['audio']['path'],
                    track['path']),
                os.path.join(path, track['path']))
            added_files += 1
        if database['music'][uuid]['artists']:
            file_artists_strs = [database['artists'][artist]['name'] + db['templates']['artist']['seperator']['filename'] for artist in database['music'][uuid]['artists']]
            file_artists_str = ''.join(file_artists_strs)
            file_artists_str = file_artists_str.strip(db['templates']['artist']['seperator']['filename'])
            meta_artists_strs = [database['artists'][artist]['name'] + db['templates']['artist']['seperator']['metadata'] for artist in database['music'][uuid]['artists']]
            meta_artists_str = ''.join(meta_artists_strs)
            meta_artists_str = meta_artists_str.strip(db['templates']['artist']['seperator']['metadata'])
        else:
            file_artists_str = 'Unknown'
            meta_artists_str = None
        if db['music']['overwrite']['metadata'] and (force_meta_write or write_changed_meta):
            print('Updating the metadata')
            file = music_tag.load_file(os.path.join(path, db['music']['tracks'][uuid]['path']))
            overwrote_meta = False
            if database['music'][uuid]['title']:
                if force_meta_write or (write_changed_meta and file['title'] != database['music'][uuid]['title']):
                    file['title'] = database['music'][uuid]['title']
                    overwrote_meta = True
            if database['music'][uuid]['artists']:
                if force_meta_write or (write_changed_meta and file['artist'] != meta_artists_str):
                    file['artist'] = meta_artists_str
                    overwrote_meta = True
            if database['music'][uuid]['album']:
                if force_meta_write or (write_changed_meta and file['album'] != database['albums'][database['music'][uuid]['album']]['name']):
                    file['album'] = database['albums'][database['music'][uuid]['album']]['name']
                    overwrote_meta = True
                if force_meta_write or (write_changed_meta and file['albumartist'] != database['artists'][database['albums'][database['music'][uuid]['album']]['artist']]['name']):
                    if database['albums'][database['music'][uuid]['album']]['artist']:
                        file['albumartist'] = database['artists'][database['albums'][database['music'][uuid]['album']]['artist']]['name']
            if database['music'][uuid]['genre']:
                if force_meta_write or (write_changed_meta and file['genre'] != database['genres'][database['music'][uuid]['genre']]['name']):
                    file['genre'] = database['genres'][database['music'][uuid]['genre']]['name']
                    overwrote_meta = True
            if file['artwork']:
                if force_meta_write or not file['artwork'].value:
                    cover = (database['music'][uuid]['album'] and database['albums'][database['music'][uuid]['album']]['cover']) or database['music'][uuid]['cover']
                    if cover:
                        cover_path = os.path.join(config['files']['images']['path'] if config['files']['images']['path'] else config['files']['audio']['path'], database['covers'][cover]['path'])
                        with open(cover_path, 'rb') as f:
                            file['artwork'] = f.read()
                        overwrote_meta = True
            if overwrote_meta:
                print('Writing changes to metadata...')
                file.save()
            updated_meta += 1
        if db['music']['overwrite']['path'] and update_path:
            print('Preparing to move the file')
            data = TemplateData()
            data['title'] = database['music'][uuid]['title'] if database['music'][uuid]['title'] else 'Unknown'
            data['artists'] = file_artists_str
            data['album'] = database['albums'][database['music'][uuid]['album']]['name'] if database['music'][uuid]['album'] else 'Unknown'
            data['albumArtist'] = database['artists'][database['albums'][database['music'][uuid]['album']]['artist']]['name'] if database['music'][uuid]['album'] and database['albums'][database['music'][uuid]['album']]['artist'] else 'Unknown'
            data['ext'] = db['music']['tracks'][uuid]['path'].split('.')[-1]
            new_path: str = db['templates']['path'] % data
            new_path = correct_path(new_path)
            new_path = os.path.relpath(new_path, os.getcwd())
            if db['music']['tracks'][uuid]['path'] == new_path:
                print('Skipped moving file')
                continue
            print('Moving file...')
            os.makedirs(pathlib.Path(path, new_path).parent, exist_ok=True)
            shutil.move(os.path.join(path, db['music']['tracks'][uuid]['path']), os.path.join(path, new_path))
            db['music']['tracks'][uuid]['path'] = new_path
            moved_files += 1
        if not running:
            break
    print('Removing empty folders')
    remove_empty_folders(path)
    print('Added %s tracks' % added_files)
    print('Updated the metadata of %s tracks' % updated_meta)
    print('Moved %s files' % moved_files)
    print('Removed %s files' % removed_files)

    print('Dumping sync database')
    with open(os.path.join(path, 'sync.pkl'), 'wb') as f:
        pickle.dump(db, f)
